#include <cstdlib>
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>
#include <iterator>
#include <numeric>

#define SPEED 0.4

using namespace std;

class Neuron 
{
    public:
        vector<double> weights;
        
        Neuron() {};
        
        Neuron(int inputs) 
        {
            srand((unsigned)(time(NULL)));
        
            //+1 for bias
            for(int i=0; i<inputs + 1; i++) 
            {
                weights.push_back((double)(rand())/(32767.0/2.0) - 1.0);
            }
        }
};
      
class Layer 
{ 
    public:
        vector<Neuron> neurons;
        vector<double> output;
        vector<double> delta;
        
        Layer() {};
        
        Layer(int neurons_num, int inputs_num) 
        {
            for(int i=0; i<neurons_num; i++) 
            {
                neurons.push_back(Neuron(inputs_num));
            }
        }
};      

class Network 
{
    public:
        vector<Layer> layers;
        int networkInputs;
        
        Network(int inputs, int hidden1, int outputs) 
        {
            networkInputs = inputs;
            layers.push_back(Layer(hidden1, inputs));
            layers.push_back(Layer(outputs, hidden1));
        }
        
        Network(char* fileName)
        {
            int layersNum, neuronsNum, weightsNum;
            double weight;
            
            std::ifstream in(fileName);
            in >> networkInputs;
            in >> layersNum;
            
            for(int i=0; i<layersNum; i++)
            {
                Layer l;
                
                in >> neuronsNum;
                in >> weightsNum;
                
                for(int j=0; j<neuronsNum; j++)
                {
                    Neuron n;
                    for(int s=0; s<weightsNum; s++)
                    {
                        in >> weight;
                        n.weights.push_back(weight);
                    }
                    l.neurons.push_back(n);
                }
                layers.push_back(l);
            }
            
            in.close();
        }
        
        double sigmoid(double num) 
        {
	       return (double)(1.0/(1.0+exp(-num)));
        }
        
        double sigmoidDerivative(double num) 
        {
	       return num * (1.0 - num);
        }
        
        double train(vector<double> initialInput, vector<double> expected) 
        {
            //forward
            vector<double>* layerInput = &initialInput;
            
            for(int i=0; i < layers.size(); i++) 
            {
                if(i > 0) layerInput = &layers[i-1].output;
                                
                Layer* layer = &layers[i];
                layer->output.clear();

                for(int j=0; j<layer->neurons.size(); j++)
                {
                    Neuron* neuron = &layer->neurons[j];
                    
                    double neuronOutput = 0.0;
                    int k;
                    for(k=0; k < (*layerInput).size(); k++) 
                    {
                        neuronOutput += (*layerInput)[k] * neuron->weights[k];
                    }
                    
                    neuronOutput += 1.0 * neuron->weights[k]; //bias
                    neuronOutput = sigmoid(neuronOutput);
                    
                    layer->output.push_back(neuronOutput);
                }
            }

            //backwards
            Layer* outputLayer = &layers[layers.size() - 1];
            outputLayer->delta.clear();
            
            for(int i=0; i<outputLayer->neurons.size(); i++)
            {                
                double neuronOutput = outputLayer->output[i];
                double neuronDelta = sigmoidDerivative(neuronOutput) * (expected[i] - neuronOutput);
                outputLayer->delta.push_back(neuronDelta);
            }
            
            for(int i=layers.size() - 2; i>=0; i--)
            {
                Layer* layer = &layers[i];
                layer->delta.clear();
                
                Layer* nextLayer = &layers[i+1];
                
                for(int j=0; j<layer->neurons.size(); j++)
                {
                    double neuronOutput = layer->output[j];
                    double neuronDelta = 0.0;
                    
                    for(int k=0; k<nextLayer->neurons.size(); k++)
                    {
                        neuronDelta += nextLayer->neurons[k].weights[j] * nextLayer->delta[k];
                    }
                    
                    layer->delta.push_back(sigmoidDerivative(neuronOutput) * neuronDelta);      
                }
            }

            //change weights
            layerInput = &initialInput;
            
            for(int i=0; i<layers.size(); i++)
            {
                Layer* layer = &layers[i];
                if(i > 0) layerInput = &layers[i-1].output;
                
                for(int j=0; j<layer->neurons.size(); j++)
                {
                    Neuron* neuron = &layer->neurons[j];
                    
                    int k;
                    for(k=0; k<(*layerInput).size(); k++)
                    {
                        neuron->weights[k] += SPEED * (*layerInput)[k] * layer->delta[j];
                    }
                    neuron->weights[k] += SPEED * 1.0 * layer->delta[j];
                }   
            }
            
            double error = 0.0;
            
            for(int i=0; i<outputLayer->output.size(); i++) {
                error += pow(outputLayer->output[i] - expected[i], 2);
            }
            
            return sqrt(error);
        }
        
        vector<double> test(vector<double> initialInput)
        {
            //forward
            vector<double>* layerInput = &initialInput;
            
            for(int i=0; i < layers.size(); i++) 
            {
                if(i > 0) layerInput = &layers[i-1].output;
                                
                Layer* layer = &layers[i];
                layer->output.clear();

                for(int j=0; j<layer->neurons.size(); j++)
                {
                    Neuron* neuron = &layer->neurons[j];
                    
                    double neuronOutput = 0.0;
                    int k;
                    for(k=0; k < (*layerInput).size(); k++) 
                    {
                        neuronOutput += (*layerInput)[k] * neuron->weights[k];
                    }
                    
                    neuronOutput += 1.0 * neuron->weights[k]; //bias
                    neuronOutput = sigmoid(neuronOutput);
                    
                    layer->output.push_back(neuronOutput);
                }
            }
            
            return layers[layers.size() - 1].output;
        }
        
        void saveWeights(char* fileName)
        {
            ofstream out(fileName);
            out << networkInputs << " " << layers.size() << endl;
            
            for(int i=0; i<layers.size(); i++)
            {
                out << layers[i].neurons.size() << " " << layers[i].neurons[0].weights.size() << endl;
                for(int j=0; j<layers[i].neurons.size(); j++)
                {
                    vector<double> v = layers[i].neurons[j].weights;
                    copy(v.begin(), v.end(), ostream_iterator<double>(out," "));
                    out << endl;
                }
            }
            
            out.close();
        }
};

int main(int argc, char *argv[])
{
    srand(time(NULL));
    
    //Network N = Network(4, 6, 3);
    Network N = Network("wagi.txt");
    vector<double> input;
    vector<double> expected;
    
    double limit = 0.01;
    bool stop = false;
    
    double error = 0.0;
    int counter = 0;

    while(!stop)
    {  
        counter++;

        double x = (double)(rand()) / RAND_MAX;

        int stage = counter%3 + 1;

        input.push_back(x);
        
        if(stage == 1)
        {
            input.push_back(1);
            input.push_back(0);
            input.push_back(0);
        }
        else if(stage == 2)
        {
            input.push_back(0);
            input.push_back(1);
            input.push_back(0);
        }
        else
        {
            input.push_back(0);
            input.push_back(0);
            input.push_back(1);
        }
        
        if(x < 0.5 / stage)
        {
            expected.push_back(1);   
            expected.push_back(0);
            expected.push_back(0);
        }
        else if(x < 0.9 / stage)
        {
            expected.push_back(0);   
            expected.push_back(1);
            expected.push_back(0);
        }
        else
        {
            expected.push_back(0);   
            expected.push_back(0);
            expected.push_back(1);
        }
        
        error += N.train(input, expected);
        
        if(counter%10 == 0)
        {
            if(error/10.0 < limit) stop=true;
            error = 0.0;
        }

        input.clear();
        expected.clear();
    }
    
    cout << counter << endl;
    
    vector<double> output;
    double sum;
    for(int i=0; i<1; i++)
    {
        double x = (double)(rand()) / RAND_MAX;

        input.push_back(x);
        input.push_back(1);
        input.push_back(0);
        input.push_back(0);
        cout << x << endl;
        output = N.test(input);
        sum = accumulate(output.begin(), output.end(), 0.0);
        cout << output[0]/sum << " " << output[1]/sum << " " << output[2]/sum << endl;   
        input.clear();
        
        input.push_back(x);
        input.push_back(0);
        input.push_back(1);
        input.push_back(0);
        output = N.test(input);
        sum = accumulate(output.begin(), output.end(), 0.0);
        cout << output[0]/sum << " " << output[1]/sum << " " << output[2]/sum << endl;   
        input.clear();
        
        input.push_back(x);
        input.push_back(0);
        input.push_back(0);
        input.push_back(1);
        output = N.test(input);
        sum = accumulate(output.begin(), output.end(), 0.0);
        cout << output[0]/sum << " " << output[1]/sum << " " << output[2]/sum << endl;    
        input.clear();
    }
    
    
    N.saveWeights("wagi.txt");
    
    system("PAUSE");
    return EXIT_SUCCESS;
}
